#include "OMIT_MeshIO.h"

#include <fstream>
#include <array>
#include <filesystem>
#include <type_traits>

#include "../../inc/TopoDS/OMIT_MeshForwardDecl.h"
#include "../../include/TopoDS/OMIT_MeshDS.h"
#include "../../include/TopoDS/OMIT_MeshFace.h"
#include "../../inc/TopoDS/OMIT_MeshForwardDecl.h"

#include "OMIT_AttrsTraits.h"
#include "OMIT_MeshAttrDS.h"
#include "OMIT_MeshAttributes.h"

#include "OMIT_MeshUtil.h"

namespace OMIT_NAMESPACE
{
    namespace FileIO
    {
        template <typename Traits>
        void WriteOFF(std::shared_ptr<TMesh<Traits>> pMesh,
                      const std::filesystem::path& filePath)
        {
            using TV = MeshTraits_V<Traits>;
            using TE = MeshTraits_E<Traits>;
            using TF = MeshTraits_F<Traits>;
            using TH = MeshTraits_H<Traits>;
            // check if the TV contains the required attributes
            static_assert(detail::_is_tuple<typename TV::value_type>::value, "Invalid type traits of value_type!");
            static_assert(detail::_is_in_tuple<geomAttr::Attr_Coord3f, typename TV::value_type>::value,
                "The Vertex doesn't has the property 'geom::Attr_Coord3f'! "
                "Please inherit Vertex from TAttrVert<Traits, geom::Attr_Coord3f,...>'!");

            // implement file format according to
            // https://segeval.cs.princeton.edu/public/off_format.html

            // OFF
            // numVertices numFaces numEdges
            // x y z
            // x y z
            // ... numVertices like above
            // NVertices v1 v2 v3 ... vN
            // MVertices v1 v2 v3 ... vM
            // ... numFaces like above

            // 例如
            // OFF
            // 8 6 0
            // -0.500000 -0.500000  0.500000
            //  0.500000 -0.500000  0.500000
            // -0.500000  0.500000  0.500000
            //  0.500000  0.500000  0.500000
            // -0.500000  0.500000 -0.500000
            //  0.500000  0.500000 -0.500000
            // -0.500000 -0.500000 -0.500000
            //  0.500000 -0.500000 -0.500000
            // 4 0 1 3 2
            // 4 2 3 5 4
            // 4 4 5 7 6
            // 4 6 7 1 0
            // 4 1 7 5 3
            // 4 6 0 2 4
            
            std::ofstream offFile(filePath);

            offFile << fmt::format("{0}\n", "OFF");

            const auto& vertList = pMesh->Vertices();
            const auto& faceList = pMesh->Faces();
            const auto& edgeList = pMesh->Edges();

            offFile << fmt::format("{0} {1} {2}\n", vertList.size(), faceList.size(), edgeList.size());

            // process vertices coordinates
            for (const TV* pV : vertList)
            {
                const auto& coord = pV->_coord3f;
                offFile << fmt::format("{0} {1} {2}\n", coord.x, coord.y, coord.z);
            }

            // process faces
            for (const TF* pF : faceList)
            {
                auto extOriginID = pF->AdjHalfEdges() 
                                | ranges::views::transform([pMesh](const TH* pH)->size_t
                                                           { return pMesh->Index(pH->Origin()); });
                offFile << fmt::format("{0} {1}\n", pF->Degree(), fmt::join(extOriginID, " "));
            }

            offFile.close();

            //spdlog::info("File has been written in path {0}", filePath.string())
        }

        template <typename Traits>
        void WriteSTL(std::shared_ptr<TMesh<Traits>> pMesh,
                      const std::filesystem::path& filePath,
                      bool writeBinary)
        {
            vtkSmartPointer<vtkSTLWriter> pSTLWriter = vtkSmartPointer<vtkSTLWriter>::New();
            pSTLWriter->SetFileName(filePath.string().c_str());
            if (writeBinary)    pSTLWriter->SetFileTypeToBinary();
            else                pSTLWriter->SetFileTypeToASCII();

            auto pPolyData = DataIO::ConvertToVTKDS(pMesh, { 0 }, { 0 });

            pSTLWriter->SetInputData(pPolyData);
            pSTLWriter->Write();
        }

        template <typename Traits>
        void WriteVTK(std::shared_ptr<TMesh<Traits>> pMesh,
                      const std::filesystem::path& filePath,
                      bool writeBinary,
                      OMIT_MESHATTR_OUTPUT_NUMOPS vertAttrOPs,
                      OMIT_MESHATTR_OUTPUT_NUMOPS faceAttrOPs)
        {
            vtkSmartPointer<vtkPolyDataWriter> pVTKWriter = vtkSmartPointer<vtkPolyDataWriter>::New();
            pVTKWriter->SetFileName(filePath.string().c_str());
            if (writeBinary)    pVTKWriter->SetFileTypeToBinary();
            else                pVTKWriter->SetFileTypeToASCII();

            auto pPolyData = DataIO::ConvertToVTKDS(pMesh, vertAttrOPs, faceAttrOPs);

            pVTKWriter->SetInputData(pPolyData);
            pVTKWriter->Write();
        }
    }
}